package comirva.audio.util;

import comirva.audio.util.math.Matrix;

/**
 * <b>PointList</b>
 *
 * <p>Description: </p>
 * Represents a list of points in an n-dimensional space. The coordinates of a
 * point are modeled as double values and the point is stored as an object of
 * type <code>Matrix</code>.
 * From the implementation point of view the list is managed by using a
 * dynamically growing array. So <code>PointList</code> is closely related to
 * <code>java.util.ArrayList</code>.
 *
 * @author Klaus Seyerlehner
 * @version 1.0
 */
public class PointList
{
  //fields
  protected int dimension;       //dimensions of the vector space
  protected int numberElements;  //number of elements currently in the list
  protected Matrix[] data;       //array holding the data-points

  //implementation details
  private double[] min;        //array of minimum values for each coordinate
  private double[] max;        //array of maximum values for each coordinate
  private Matrix sum;          //sum over all elements in this list
  private Matrix squaredSum;   //squared sum over all elements in this list



  /**
   * Creates a new point list with default capacity.
   *
   * @param dimension int initial capacity of the array holding the points; must
   *                      be at least one
   * @throws IllegalArgumentException raised if mehtod contract is violated
   */
  public PointList(int dimension) throws IllegalArgumentException
  {
    this(dimension, 16384);
  }


  /**
   * Creates a new point list.
   *
   * @param dimension int dimension of the vector space; must be at least one
   * @param capacity int initial capacity of the array holding the points must
   *                     be at least one
   * @throws IllegalArgumentException raised if mehtod contract is violated
   */
  public PointList(int dimension, int capacity) throws IllegalArgumentException
  {
    //check paramters
    if(dimension < 1)
      throw new IllegalArgumentException("the dimension must be at least one");
    if(capacity < 1)
      throw new IllegalArgumentException("the initial capacity must be at least one");

    //initialize fields
    this.data = new Matrix[capacity];
    this.dimension = dimension;
    this.min = new double[dimension];
    this.max = new double[dimension];
    this.sum = new Matrix(dimension, 1);
    this.squaredSum = new Matrix(dimension, 1);
  }


  /**
   * Adds a data-point to the list.
   *
   * @param point double[] the data point
   * @throws IllegalArgumentException
   */
  public void add(double[] point) throws IllegalArgumentException
  {
    //check the point
    if(point == null || point.length != dimension)
      throw new IllegalArgumentException("data point must not be a null value and dimension must agree");

    //keep track over minimum and maximum values
    for(int i = 0; i < dimension; i++)
    {
      if(point[i] > max[i])
        max[i] = point[i];
      if(point[i] < min[i])
        min[i] = point[i];
    }

    //create matrix object for this point
    Matrix x = new Matrix(point, dimension);

    //update sum and squared sum
    sum.plusEquals(x);
    squaredSum.plusEquals(x.pow(2.0d));

    //add the point to the list
    data[numberElements] = x;
    numberElements++;

    //ajust data array, if necessary
    if(numberElements == data.length)
      resize();
  }


  /**
   * Returns the minimum value for each coordinat observed up to now.
   *
   * @return double[] array containing the minimum values; the first element is
   *                  the minimum of the first coordinat observed, etc.
   */
  public double[] getMinPoint()
  {
    return min;
  }


  /**
   * Returns the maximum value for each coordinat observed up to now.
   *
   * @return double[] array containing the maximum values; the first element is
   *                  the maximum of the first coordinat observed, etc.
   */
  public double[] getMaxPoint()
  {
    return max;
  }


  /**
   * Returns the dimension of the points/vectors in this list.
   *
   * @return int dimension of the points in this list
   */
  public int getDimension()
  {
    return dimension;
  }


  /**
   * Returns the number of points in this list.
   *
   * @return int
   */
  public int size()
  {
    return numberElements;
  }


  /**
   * Returns the i-th point in ths list.
   *
   * @param i int must be a vaild index
   * @return Matrix the point encapsulated in a Matrix object
   * @throws IndexOutOfBoundsException if i is no a valid index
   */
  public Matrix get(int i) throws IndexOutOfBoundsException
  {
    return data[i];
  }


  /**
   * Returns a vector containing the means of each coordinate.
   *
   * @return Matrix mean vector
   */
  public Matrix getMean()
  {
    return sum.times(1.0d/numberElements);
  }


  /**
   * Returns a vector containing the variance of each coordinate.
   *
   * @return Matrix variance vector
   */
  public Matrix getVariance()
  {
    return squaredSum.times(1.0d/numberElements).minus(getMean().pow(2.0));
  }


  /**
   * Returns a new <code>PointList</code> containing normalized points. The
   * normalization is done for every coordinate by subtracting the mean and
   * dividing by the standard deviation. This is also known as z-normalization.
   * The original point list is unchanged.
   *
   * @return PointList the original point list
   */
  public PointList normalize()
  {
    //get mean, varaince and standard deviation for normalization
    Matrix mean = getMean();
    Matrix variances = getVariance();
    Matrix stdDeviation = variances.pow(0.5d);

    //create point list to return
    PointList normalized = new PointList(dimension);

    //normalize all points and add them to the new list
    for(int i = 0; i < numberElements; i++)
    {
      Matrix x = data[i];
      x = x.minus(mean);
      x.arrayRightDivideEquals(stdDeviation);
      normalized.add(x.getColumnPackedCopy());
    }

    return normalized;
  }


  /**
   * This method is for debugging purpose only. The complete list will be
   * printed using the standard output stream.
   */
  public void print()
  {
    System.out.println("{");
    for(int i = 0; i < data.length; i++)
    {
      Matrix x = data[i];
      System.out.print("{");
      for(int j = 0; j < dimension; j++)
      {
        if(j < dimension-1)
          System.out.print(x.get(j, 0) + ",");
        else
          System.out.print(x.get(j, 0));
      }
      if(i < data.length-1)
        System.out.println("},");
      else
        System.out.println("}");
    }
    System.out.println("}");
  }


  /**
   * Resizes the internal array holding all the point matrices. The size of the
   * array after the call will be twice the current size. All elements of the
   * current array will be copied.
   */
  private void resize()
  {
    Matrix[] newData = new Matrix[data.length*2];

    for(int i = 0; i < data.length; i++)
      newData[i] = data[i];

    data = newData;
  }
}
